package cn.lg.soar.redis.aspect.clusterlock;

import cn.lg.soar.redis.annotations.ClusterFairLock;
import cn.lg.soar.redis.annotations.ClusterLock;
import cn.lg.soar.redis.annotations.ClusterReadLock;
import cn.lg.soar.redis.annotations.ClusterWriteLock;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.springframework.core.annotation.Order;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: luguoxiang
 * @Date: 2021-05-18 15:41
 * @Description: 分布式锁aop实现
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Aspect
@Order(0)
public class ClusterLockAspect {

    private final Map<ClusterLock, IConfig> CACHE = new ConcurrentReferenceHashMap<>();
    private final Map<ClusterFairLock, IConfig> FAIR_CACHE = new ConcurrentReferenceHashMap<>();
    private final Map<ClusterReadLock, IConfig> RCACHE = new ConcurrentReferenceHashMap<>();
    private final Map<ClusterWriteLock, IConfig> WCACHE = new ConcurrentReferenceHashMap<>();

    @Resource
    private Redisson redisson;

    @Around("@annotation(clusterLock)")
    public Object around(ProceedingJoinPoint point, ClusterLock clusterLock) throws Throwable {

        // 获取配置
        IConfig config = CACHE.get(clusterLock);
        if (null == config) {
            synchronized (ClusterLock.class) {
                config = CACHE.get(clusterLock);
                if (config == null) {
                    // 构建配置
                    config = IConfig.build(point, "lock:" + clusterLock.value(), clusterLock.key());
                    CACHE.put(clusterLock, config);
                }
            }
        }

        RLock lock = this.redisson.getLock(config.lockName(point));
        try{
            // 获取锁
            if (lock.tryLock(clusterLock.waitTime(), clusterLock.leaseTime(), TimeUnit.MILLISECONDS)) {
                return point.proceed();
            }
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        throw new RuntimeException(clusterLock.message());
    }

    @Around("@annotation(clusterLock)")
    public Object around(ProceedingJoinPoint point, ClusterFairLock clusterLock) throws Throwable {

        // 获取配置
        IConfig config = FAIR_CACHE.get(clusterLock);
        if (null == config) {
            synchronized (ClusterFairLock.class) {
                config = FAIR_CACHE.get(clusterLock);
                if (config == null) {
                    // 构建配置
                    config = IConfig.build(point, "lock:" + clusterLock.value(), clusterLock.key());
                    FAIR_CACHE.put(clusterLock, config);
                }
            }
        }

        RLock lock = this.redisson.getFairLock(config.lockName(point));
        try{
            // 获取锁
            if (lock.tryLock(clusterLock.waitTime(), clusterLock.leaseTime(), TimeUnit.MILLISECONDS)) {
                return point.proceed();
            }
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        throw new RuntimeException(clusterLock.message());
    }

    @Around("@annotation(clusterLock)")
    public Object around(ProceedingJoinPoint point, ClusterReadLock clusterLock) throws Throwable {

        // 获取配置
        IConfig config = RCACHE.get(clusterLock);
        if (null == config) {
            synchronized (ClusterReadLock.class) {
                config = RCACHE.get(clusterLock);
                if (config == null) {
                    // 构建配置
                    config = IConfig.build(point, "lock:" + clusterLock.value(), clusterLock.key());
                    RCACHE.put(clusterLock, config);
                }
            }
        }

        RReadWriteLock readWriteLock = this.redisson.getReadWriteLock(config.lockName(point));
        RLock lock = readWriteLock.readLock();
        try{
            // 获取锁
            if (lock.tryLock(clusterLock.waitTime(), clusterLock.leaseTime(), TimeUnit.MILLISECONDS)) {
                return point.proceed();
            }
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        throw new RuntimeException(clusterLock.message());
    }

    @Around("@annotation(clusterLock)")
    public Object around(ProceedingJoinPoint point, ClusterWriteLock clusterLock) throws Throwable {

        // 获取配置
        IConfig config = WCACHE.get(clusterLock);
        if (null == config) {
            synchronized (ClusterWriteLock.class) {
                config = WCACHE.get(clusterLock);
                if (config == null) {
                    // 构建配置
                    config = IConfig.build(point, "lock:" + clusterLock.value(), clusterLock.key());
                    WCACHE.put(clusterLock, config);
                }
            }
        }

        RReadWriteLock readWriteLock = this.redisson.getReadWriteLock(config.lockName(point));
        RLock lock = readWriteLock.writeLock();

        try{
            // 获取锁
            if (lock.tryLock(clusterLock.waitTime(), clusterLock.leaseTime(), TimeUnit.MILLISECONDS)) {
                return point.proceed();
            }
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        throw new RuntimeException(clusterLock.message());
    }

}
